// SPDX-License-Identifier: GPL-2.0

#define pr_fmt(fmt) "io-emulator: " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/percpu.h>
#include <linux/sizes.h>
#include <linux/version.h>
#include <linux/io.h>

#include "mod_detector.h"
#include "trap_io.h"
#include "error-inject.h"

static inline int emulation_write(mmio_op_t *io)
{
    int status = STATUS_OK;
    uint64_t err_value;

    if (should_error_inject(io->address, io->access_size, true, &err_value)) {
        io->value = err_value;
    }

    switch (io->access_size) {
    case 8:
        writeb(io->value, io->address);
        break;        
    case 16:
        writew(io->value, io->address);
        break;        
    case 32:
        writel(io->value, io->address);
        break;        
    case 64:
        writeq(io->value, io->address);
        break;
    default:
        return STATUS_FAIL;
    }

    return status;
}

static inline int emulation_read(mmio_op_t *io)
{
    int status = STATUS_OK;
    uint64_t err_value;

    switch (io->access_size) {
    case 8:
        io->value = readb(io->address);
        break;        
    case 16:
        io->value = readw(io->address);
        break;        
    case 32:
        io->value = readl(io->address);
        break;        
    case 64:
        io->value = readq(io->address);
        break;
    default:
        return STATUS_FAIL;
    }

    if (should_error_inject(io->address, io->access_size, false, &err_value)) {
        io->value = err_value;
    }

    return status;
}
/* Handle this MMIO operation */
static int io_emulation_handler(mmio_op_t *io)
{
    if (io->is_store) {
        pr_debug("Writing 0x%llx to 0x%llx\n", io->value, (uint64_t)io->address);
        emulation_write(io);
    } else {
        emulation_read(io);
        pr_debug("Reading 0x%llx from 0x%llx\n", io->value, (uint64_t)io->address);
    }
    return STATUS_OK;

    /* Invalid memory region */
}

static int __init io_emulation_init(void)
{
    module_detector_init();
    trap_io_init(&io_emulation_handler);
    error_inject_debugfs_init();

    return 0;
}

static void __exit io_emulation_exit(void)
{
    module_detector_deinit();
    trap_io_deinit();
}

module_init(io_emulation_init);
module_exit(io_emulation_exit);
MODULE_LICENSE("GPL v2");
